#include "xml_config.h"
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <gsl/gsl_const_mksa.h>


/**
 * Gets the specified list of parameters from XML node and parses them 
 * as double.
 * @param node - XML node to study
 * @param names - field names
 * @param values - array of values to save data for each field to
 * @param N - number of fields to retrieve
 * @return 0 - OK, > 0 - (offset + 1) of parameter, that has not been found
 */
static uint8_t get_double_params(xmlNodePtr node, const char ** names, double * values, uint8_t N){
	uint8_t ksi;
	for(ksi = 0; ksi < N; ksi += 1){
		char * val = xmlGetProp(node, names[ksi]);
		if(val == NULL){
			return ksi + 1;
		}
		values[ksi] = atof(val);
		xmlFree(val);
	}
	return 0;
}


/**
 * Checks if node's field 'name' exists, and compares it's value against
 * the given array of values.
 * @param node - XML node to study
 * @param name - field name
 * @param val - values to check field against
 * @param N - number of values to compare to
 * @return 0 - field not found
 *         1 - field doesn't match anything
 *         (2 + offset) - field matches val, denoted by offset
 */ 
static uint8_t node_param_selector(xmlNodePtr node, const char * name, const char ** val, uint8_t N){
	char * field_val = xmlGetProp(node, name);
	if(NULL == field_val){
		return 0;
	}
	
	uint8_t k;
	boolean found=FALSE;
	for(k=0;k<N;k+=1){
		if(!strcmp(val[k],field_val)){
			found=TRUE;
			break;
		}
	}
	xmlFree(field_val);
	if(!found){
		return 1;
	}
	return (2 + k);
}


/**
 * Reads vector like "[double, double, double]" from the specified 
 * parameter. Every other value, successfully parsed by sscanf (like
 * " [1.0,1.0,1.0 "), would also be OK.
 * @param node - node to study parameter of
 * @param name - parameter name
 * @param vec - double[3] vector to put data to
 * @return 0 - OK, 1 - parameter not found, 2 - failed to parse
 */
static uint8_t node_get_vec(xmlNodePtr node, const char * name, double * vec){
	char * field_val = xmlGetProp(node, name);
	if(NULL == field_val){
		return 1;
	}
	int sscan_res = sscanf(field_val, "[%lf,%lf,%lf]", vec, vec + 1, vec + 2);
	xmlFree(field_val);
	if(sscan_res != 3){
		return 2;
	}
	return 0;
}

/**
 * Like get_double_params, but with vectors. What's vector is described
 * in node_get_vec.
 * @param node - node to get values from
 * @param names - parameter names
 * @param vecs - double[3] vectors to put data to
 * @param N - number of values in names and vecs
 * @param failcode - return code from node_get_vec if the whole fails
 * @return 0 - OK, all values retrieved
 * (1 + offset) - failed getting variable, identified by offset
 */
static uint8_t get_vec_params(xmlNodePtr node, const char ** names, 
	double ** vecs, uint8_t N, uint8_t * failcode){
	uint8_t j;
	for(j=0; j<N; j+=1){
		uint8_t parser_rv = node_get_vec(node, names[j], vecs[j]);
		if(parser_rv!=0){
			* failcode = parser_rv;
			return 1 + j;
		}
	}
	return 0;
}


void discharge_free(struct discharge * dis){
		trajectory_len_free( & dis -> trlen);
}

// <discharge (trajectory="line" r0="[,,]" r1="[,,]"|trajectory="bezier2" P0 P1 P2) 
//(current="expexp" alpha= beta= eta= v_c= Q=|current="powexp" alpha= n= eta= v_c= Q=) />

/**
 *  <discharge t_start="..." >
 * 		<current>
*  			<shape />
 *  		<delay />
 *  		<decrease />
 * 		</current>
 * 		<trajectory (type="line" r0="[,,]" r1="[,,]"|trajectory="bezier2" P0 P1 P2) />
 *  </discharge>
 **/

/**
 * Fills current structure by given xml node.
 *
 * @param node - root xml node
 * @param crnt - current structure
 * @return 0 - OK, not 0 - missing parameters (with info to stderr) ( codes < 1000 )
 */
static int study_current_node(xmlNodePtr node, struct current * crnt){
	xmlNodePtr level1_node;
	boolean phi=FALSE, tau=FALSE, ksi=FALSE;// Found or not?
	for(level1_node = node -> children; level1_node != NULL; level1_node = level1_node -> next){
		if(!strcmp(level1_node -> name, "shape")){
			if(phi){
				fprintf(stderr, "'shape' redefined\n");
				return 50;
			}
			phi = TRUE;
			{//phi - shape
				const char * types[] = {"expexp", "powexp"};
				uint8_t n = node_param_selector(level1_node, "type", types, array_length(types));
				if(n < 2){
					if(n){
						fprintf(stderr, "'type' of <shape> is of unknown type\n");
					}else{
						fprintf(stderr, "'type' not found in <shape>\n");
					}
					return 700;
				}
				switch(n - 2){
					case 0:{
						crnt -> shape.id = CURRENT_SHAPE_EXPEXP;
						const char * names[] = {"alpha", "beta", "Q"};
						double values[array_length(names)];
						uint8_t n = get_double_params(level1_node, names, values, array_length(names));
						if(n){
							fprintf(stderr, "Parameter '%s' not found in <shape>\n", names[n-1]);
							return 701;
						}
						crnt -> shape.expexp.alpha = values[0];
						crnt -> shape.expexp.beta = values[1];
						crnt -> shape.expexp.Q = values[2];
					}
					break;
					case 1:{
						crnt -> shape.id = CURRENT_SHAPE_POWEXP;
						const char * names[] = {"alpha", "n", "Q"};
						double values[array_length(names)];
						uint8_t n = get_double_params(level1_node, names, values, array_length(names));
						if(n){
							fprintf(stderr, "Parameter '%s' not found in <shape>\n", names[n-1]);
							return 702;
						}
						
						crnt -> shape.powexp.alpha = values[0];
						uint8_t n_p = (uint8_t) values[1];
						if(n_p != values[1]){
							fprintf(stderr, "Parameter 'n' must be positive, small integer\n");
							return 710;
						}
						crnt -> shape.powexp.n = n_p;
						crnt -> shape.powexp.Q = values[2];
					}
					break;
					default:
						assert(FALSE);
				}
				
			}
		}else if(!strcmp(level1_node -> name, "delay")){
			if(tau){
				fprintf(stderr, "'delay' redefined\n");
				return 51;
			}
			tau = TRUE;
			{//tau - delay
				const char * types[] = {"linear", "exponential"};
				uint8_t n = node_param_selector(level1_node, "type", types, array_length(types));
				if(n < 2){
					if(n){
						fprintf(stderr, "'type' of <delay> is of unknown type\n");
					}else{
						fprintf(stderr, "'type' not found in <delay>\n");
					}
					return 500;
				}
				switch(n - 2){
					case 0:{
						crnt -> delay.id = CURRENT_DELAY_LINEAR;
						const char * names[] = {"v_c"};
						double v_c;
						uint8_t n = get_double_params(level1_node, names, &v_c, 1);
						if(n){
							fprintf(stderr, "'v_c' not found in <delay>\n");
							return 501;
						}
						crnt -> delay.linear.v0 = v_c * GSL_CONST_MKSA_SPEED_OF_LIGHT;
					}
					break;
					case 1:{
						crnt -> delay.id = CURRENT_DELAY_EXPONENTIAL;
						const char * names[] = {"v_c", "gamma"};
						double values[array_length(names)];
						uint8_t n = get_double_params(level1_node, names, values, array_length(names));
						if(n){
							fprintf(stderr, "'%s' not found in <delay>\n", names[n-1]);
							return 502;
						}
						crnt -> delay.exponential.gamma = values[1];
						crnt -> delay.exponential.v0 = values[0] * GSL_CONST_MKSA_SPEED_OF_LIGHT;
					}
					break;
					default:
						assert(FALSE);
				}
			}
		}else if(!strcmp(level1_node -> name, "decrease")){
			if(ksi){
				fprintf(stderr, "'decrease' redefined\n");
				return 52;
			}
			ksi = TRUE;
			{//ksi - decrease
				const char * types[] = {"none", "exponential"};
				uint8_t n = node_param_selector(level1_node, "type", types, array_length(types) );
				if(n < 2){
					// error
					if(n){
						fprintf(stderr, "'type' not found in 'ksi'\n");
					}else{
						fprintf(stderr, "'type' contains something strange in 'ksi'\n");
					}
					return 20;
				}
				switch(n - 2){
					case 0:{
						crnt -> decrease.id = CURRENT_DECREASE_NONE;
						// Done
					}
					break;
					case 1:{
						crnt -> decrease.id = CURRENT_DECREASE_EXPONENTIAL;
						const char * names[] = {"eta"};
						double values[array_length(names)];
						uint8_t n = get_double_params(level1_node, names, values, array_length(names));
						if(n){
							fprintf(stderr, "'%s' not found in decrease\n", names[n-1]);
							return 30;
						}
						crnt -> decrease.exponential.eta = values[0];
					}
					break;
					default:
						assert(FALSE);
				}
			}
		}else if(!strcmp(level1_node -> name, "text")){
			// Happens everywhere
		}else{
			fprintf(stderr, "Unknown chield <%s> in <discharge>\n", level1_node -> name);
			return 100;
		}
	}
	//Checking if all necessary objects have been found in
	if(!(phi && ksi && tau)){
		if(!phi){
			fprintf(stderr, "<shape ...> not found\n");
		}
		if(!ksi){
			fprintf(stderr, "<decrease ...> not found\n");
		}
		if(!tau){
			fprintf(stderr, "<delay ...> not found\n");
		}
		return 980;
	}
	
	// Done
	return 0; 
}




/**
 * Fills trj structure by given <trajectory> node
 * 
 * @param node - xml node to get trajectory data from
 * @param trj - trajectory object to construct
 * @return 0 - OK, error statuses < 1000 if error
 */
static int study_trajectory_node(node, trj)
xmlNodePtr node;
union trajectory * trj; // In the past times people wrote like this :D
{
	/*
	 *  Retrieving trajectory data
	 */
	const char * trajectories[] = {"line", "bezier2", "bezier3"}; 
	uint8_t ntr = array_length(trajectories);
	uint8_t n = node_param_selector(node, "type", trajectories, ntr);
	if(n < 2){
		if(0 == n){
			fprintf(stderr, "'type' field not found\n");
		}else{
			fprintf(stderr, "'type' must be one of: ");
			uint8_t l;
			for(l=0;l<ntr;l+=1){
				fprintf(stderr, "\"%s\"", trajectories[l]);
				if(l!=ntr-1){
					fprintf(stderr, ", ");
				}
			}
			fprintf(stderr, ".\n");
		}
		return 10;
	}
	n -= 2; // Now it's OFFSET
	if(n == 0){ // line
		double r0[3], r1[3];
		double * vecs[] = {r0,r1};
		const char * names [] = {"r0", "r1"};
		uint8_t parser_fail = 0;
		uint8_t get_fail = get_vec_params(node, names, vecs, array_length(names), &parser_fail);
		if(get_fail){
			fprintf(stderr, "line: Failed retrieving %s\n (parser code %i)\n", names[get_fail - 1], parser_fail);
			return 20;
		}
		trj -> id = TRAJ_LINE;
		copyvec(trj -> line . r0, r0);
		copyvec(trj -> line . r1, r1);
	}else if(n == 1){ // bezier2
		double P0[3], P1[3], P2[3];
		double * vecs[] = {P0, P1, P2};
		const char * names [] = {"P0", "P1", "P2"};
		uint8_t parser_fail = 0;
		uint8_t get_fail = get_vec_params(node, names, vecs, array_length(names), &parser_fail);
		if(get_fail){
			fprintf(stderr, "bezier2: Failed retrieving %s\n (parser code %i)\n", names[get_fail - 1], parser_fail);
			return 30;
		}
		trj -> id = TRAJ_BEZIER2;
		copyvec(trj -> bezier2.P0, P0);
		copyvec(trj -> bezier2.P1, P1);
		copyvec(trj -> bezier2.P2, P2);
	}else{ // bezier3
		double P0[3], P1[3], P2[3], P3[3];
		double * vecs[] = {P0, P1, P2, P3};
		const char * names[] = {"P0", "P1", "P2", "P3"};
		uint8_t parser_fail = 0;
		uint8_t get_fail = get_vec_params(node, names, vecs, array_length(names), &parser_fail);
		if(get_fail){
			fprintf(stderr, "bezier3: Failed retrieving %s\n (parser code %i)\n", names[get_fail - 1], parser_fail);
			return 40;
		} 
		trj -> id = TRAJ_BEZIER3;
		copyvec(trj -> bezier3.P0, P0);
		copyvec(trj -> bezier3.P1, P1);
		copyvec(trj -> bezier3.P2, P2);
		copyvec(trj -> bezier3.P3, P3);
	}
	return 0;
}


/**
 * Studies discharge node, constructing discharge object.
 * @param node - root node
 * @param dsg - discharge structure
 * @return 0 - OK, > 0 - error with info on stderr
 */
static int study_discharge_node(xmlNodePtr node, struct discharge * dsg){
	xmlNodePtr level1;
	boolean current_found = FALSE, trajectory_found = FALSE;
	for(level1 = node -> children; level1 != NULL; level1 = level1 -> next){
		if(!strcmp(level1 -> name, "current")){
			if(current_found){
				fprintf(stderr, "<current> redeclared\n");
				return 1000;
			}
			current_found = TRUE;
			{//current
				int s = study_current_node(level1, & dsg -> crnt);
				if(s){
					return 2000 + s;
				}
			}
		}else if(!strcmp(level1 -> name, "trajectory")){
			if(trajectory_found){
				fprintf(stderr, "<trajectory> redeclared\n");
				return 3000;
			}
			trajectory_found = TRUE;
			{//trajectory
				int s = study_trajectory_node(level1, & dsg -> trj);
				if(s){
					return 4000 + s;
				}
			}
		}else if(!strcmp(level1 -> name, "text")){
			//Skip
		}else{
			fprintf(stderr, "Strange node in <discharge>: <%s>\n", level1 -> name);
			return 8000;
		}
	}
	
	
	// Did we find everything we needed?
	if( ! (current_found && trajectory_found) ){
		fprintf(stderr, "Error parsing <discharge>, missing child nodes:\n");
		if( ! current_found ){
			fprintf(stderr, "<current> missing\n");
		}
		if( ! trajectory_found ){
			fprintf(stderr, "<trajectory> missing\n");
		}
		return 9000;
	}
	
	
	{
		// getting t_start
		double vals[1];
		const char * names[] = {"t_start"};
		uint8_t gr = get_double_params(node, names, vals, 1);
		if(gr){
			fprintf(stderr, "can't find t_start\n");
			return 100;
		}
		double t_start = vals[0];
		if (t_start < 0){
			fprintf(stderr, "t_start must be positive\n");
			return 101;
		}
		dsg -> t_start = t_start;
	}
	
	
	// Creating length object for the trajectory 
	// We do it in the end, because otherwise we would need to free
	// the object at every error return
	int lenstat = trajectory_len_create(& dsg -> trlen, & dsg -> trj);
	if(lenstat){
		fprintf(stderr, "Can't create t -> length mapping object for the trajectory, constructor 'trajectory_len_create' returned %i\n", lenstat);
		return 300;
	}
	
	
	//All OK
	return 0;
}





struct clean_up_struct { // Things that need to be cleaned up in xml_config_parser
	xmlDocPtr doc;
	struct discharge ** discharge_list_root;
};

static void clean_up(struct clean_up_struct * C){
	xmlFreeDoc(C -> doc); //It's never NULL
	
	if(C -> discharge_list_root != NULL){
		struct discharge * dis = * C -> discharge_list_root;
		trash_discharge_list(dis);
	}
	
}

int xml_config_parser(struct configuration * cfg, int file_desc){
	LIBXML_TEST_VERSION /* xml library checking */
	
	cfg -> has_mirror = FALSE;// No mirror by default
	cfg -> discharges = NULL;//Yet empty
	
	xmlDocPtr doc;
	xmlNodePtr cur;
	doc = xmlReadFd(file_desc,NULL,NULL,0);
	if (NULL == doc) {
		return 1;
	}
	struct clean_up_struct C;
	C . doc = doc; // To be cleaned up
	C . discharge_list_root = & cfg -> discharges; // Queue beginning (NULL for now, but may change later)
	cur = xmlDocGetRootElement(doc);
	if (cur == NULL) { // Empty?
		clean_up( &C );
		return 1;
	}
	
	if(strcmp(cur -> name, "model")){
		fprintf(stderr, "Rood node isn't 'model'\n");
		clean_up( &C );
		return 3;
	}
	xmlNodePtr l1cur;
	boolean observer_found = FALSE, source_found = FALSE;
	for(l1cur = cur -> children; l1cur!=NULL; l1cur = l1cur -> next){//Level 1 children iterator
		if(!strcmp(l1cur -> name, "observer")){
			if(!observer_found){//Study the observer
				observer_found = TRUE;
				// zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 888
				// r,n
				const char * names[] =  {"r","n"};
				uint8_t nn = 2;
				double r[3], n[3];
				double * vals[] = {r,n};
				uint8_t fc;
				uint8_t fail = get_vec_params(l1cur,names,vals,nn,&fc);
				if(fail){
					fprintf(stderr, "Failed to get field '%s' (parser error code %i)\n", names[fail-1], fc);
					clean_up( &C );
					return 3;
				}
				// field
				xmlChar * fval = xmlGetProp(l1cur, "field");
				uint8_t fvalr;
				if(!strcmp(fval,"H")){
					fvalr = 1;
				}else if(!strcmp(fval,"E")){
					fvalr = 2;
				}else{
					fvalr = 0;// Undefined
				}
				xmlFree(fval);
				if(!fvalr){
					fprintf(stderr, "Wrong 'field' value\n");
					clean_up( &C );
					return 3;
				}
				// Saving what we've got
				copyvec(cfg -> n, n);
				copyvec(cfg -> r, r);
				
				if(fvalr == 2){
					cfg -> field_class = FIELD_E;
				}else{
					cfg -> field_class = FIELD_H;
				}
				// Done
			}else{
				fprintf(stderr, "<observer> allready declared\n");
				clean_up( &C );
				return 3;
			}
			continue;
		}
		if(!strcmp(l1cur -> name, "source")){
			if(!source_found){//Study the source
				source_found = TRUE;
				xmlNodePtr l2cur;
				boolean discharges_found = FALSE;
				for(l2cur = l1cur -> children; l2cur!=NULL; l2cur = l2cur -> next){
					if(!strcmp(l2cur -> name, "discharge")){
						discharges_found = TRUE;
						struct discharge * dsg = malloc(sizeof(struct discharge));
						if(NULL == dsg){
							clean_up( &C );
							return 2;
						}
						int study_r = study_discharge_node(l2cur, dsg);
						
						if(study_r){
							free(dsg); // Clean-up of local resource
							clean_up( &C ); // Clean-up of all global resources
							return 3;
						}
						
						// node OK, dsg data in the structure, linking
						dsg -> next = cfg -> discharges;
						cfg -> discharges = dsg;
						continue;
					}
					if(!strcmp(l2cur -> name, "mirror")){
						cfg -> has_mirror = TRUE;
						continue;
					}
					if(!strcmp(l2cur -> name, "text")){
						// Many of them everywhere
						continue;
					}
					// Wrong node
					fprintf(stderr, "Found strange node %s inside <source> \n", l2cur -> name);
					clean_up( &C );
					return 3;
				}
				if(!discharges_found){
					fprintf(stderr, "Not a single <discharge> found inside <source>\n");
					clean_up( &C );
					return 3;
				}
			}else{
				fprintf(stderr, "<source> allready declared\n");
				clean_up( &C );
				return 3;
			}
			continue;
		}
		if(!strcmp(l1cur -> name, "text")){
			// Just text, there are many of them everywhere
			continue;
		}
		// Something strange found!
		fprintf(stderr, "Found strange node %s inside <model> \n", l1cur -> name);
		clean_up( &C );
		return 3;
	}// Done level 1 children iterator
	
	//Check
	if(!observer_found || !source_found) {
		if(!observer_found){
			fprintf(stderr, "<observer> not found\n");
		}
		if(!source_found){
			fprintf(stderr, "<source> not found\n");
		}
		clean_up( &C );
		return 3;
	}
	
	// Removing discharge linked array from remove tasks so that it will not get cleaned
	// Cleaning all the other is just what we want
	C . discharge_list_root = NULL;
	clean_up( &C );
	return 0;
}



void configuration_free(struct configuration * cfg){
	struct discharge * dis;
	dis = cfg -> discharges;
	trash_discharge_list(dis);
}


int discharge_mirror(const struct discharge * dis, struct discharge * refl){
	trajectory_mirror( & dis -> trj, & refl -> trj);
	current_mirror( & dis -> crnt, & refl -> crnt);
	refl -> t_start = dis -> t_start;
	int cr = trajectory_len_copy( & dis -> trlen, & refl -> trlen); // Because mirroring doesn't alter length function
	if(cr){
		return 1;
	}
	return 0;
}

void trash_discharge_list(struct discharge * root){
	struct discharge * r = root;
	while(r!=NULL){
		struct discharge * old;
		old = r;
		r = r -> next;
		discharge_free(old);
		free(old);
	}
}

